import { PointerDragBehavior } from "@babylonjs/core/Behaviors/Meshes/pointerDragBehavior";
import { AbstractDragConfig, IDragEventData, IDragProgressEventData } from "./config/abstract-drag-config";
import { AbstractDragLimit } from "./limit/abstract-drag-limit";
import { DragCommonConfig } from "./config/drag-common-config";
import { DragNoneLimit } from "./limit/drag-none-limit";

/**
 * 拖动的组成部件
 * @param config 拖动配置
 * @param limit 拖动约束
 * @param behavior 拖动行为
 */
interface IDragComponents {
    config: AbstractDragConfig<any>,
    limit: AbstractDragLimit,
    behavior: PointerDragBehavior
}

/**
 * 拖动回调函数
 * @param target 拖动对象
 * @param limit 约束
 * @param config 配置
 * @param behavior 行为
 */
interface IDragCallback {
    (target: any, limit: AbstractDragLimit, config: AbstractDragConfig<any>, behavior: PointerDragBehavior, eventData: IDragEventData | IDragProgressEventData): void
}

/**
 * 指针拖动
 */
class Drag {

    private _targets: Array<any> = [];
    private _configs: Array<AbstractDragConfig<any>> = [];
    private _limits: Array<AbstractDragLimit> = [];
    private _behaviors: Array<PointerDragBehavior> = [];
    private _dragStartCall: IDragCallback | null = null;
    private _dragProgressCall: IDragCallback | null = null;
    private _dragEndCall: IDragCallback | null = null;
    private _enable: boolean = true;

    /**
     * 获取拖动实例是否启用
     */
    public get enable(): boolean {
        return this._enable;
    }

    /**
     * 启用/禁用当前拖动实例
     */
    public set enable(enable: boolean) {
        this._enable = enable;
        this._configs.forEach(conf => conf.enable = enable);
    }

    /**
     * 设置拖动开始回调函数
     */
    public set onDragStart(func: IDragCallback) {
        this._dragStartCall = func;
        for (const config of this._configs) {
            config.onDragStart = this._dragStartCall;
        }
    }

    /**
     * 设置拖动过程中回掉函数
     */
    public set onDragProgress(func: IDragCallback) {
        this._dragProgressCall = func;
        for (const config of this._configs) {
            config.onDragProgress = this._dragProgressCall;
        }
    }

    /**
     * 设置拖动结束回掉函数
     */
    public set onDragEnd(func: IDragCallback) {
        this._dragEndCall = func;
        for (const config of this._configs) {
            config.onDragEnd = this._dragEndCall;
        }
    }

    /**
     * 重置所有拖动行为
     */
    public resetBehaviors(): void {
        const tars = this._targets;
        const cnfs = this._configs;
        const bhvs = this._behaviors;
        for (let i = 0; i < cnfs.length; i++) {
            cnfs[i].end(tars[i], bhvs[i]);
        }
        bhvs.length = 0;
        for (let i = 0; i < tars.length; i++) {
            const bhv = cnfs[i].begin(tars[i]);
            bhvs[i] = bhv;
        }
    }

    /**
     * 添加目标为可拖动对象
     * @param target 目标
     * @param dragConfig 拖动配置
     * @param limit 拖动约束
     */
    public addTarget(target: any, dragConfig?: AbstractDragConfig<any>, dragLimit?: AbstractDragLimit): void {
        const config = dragConfig || new DragCommonConfig();
        const limit = dragLimit || new DragNoneLimit();
        const configs = this._configs;
        const limits = this._limits;
        for (const conf of configs) {
            if (config.id === conf.id) {
                console.warn('cannot add one [dragConfig] to [Drag] for twice!', dragConfig);
                return;
            }
        }
        for (const lim of limits) {
            if (limit.id === lim.id) {
                console.warn('cannot add one [dragLimit] to [Drag] for twice!', dragLimit);
                return;
            }
        }
        if (!config.test(target)) {
            console.warn('[target] doesnot match [config]!', target, config);
            return;
        }

        const targets = this._targets;
        const index = this.findTarget(target);
        if (index >= 0) {
            console.warn('cannot add one [target] to [Drag] for twice!', target);
            return;
        }

        targets.push(target);
        configs.push(config);
        config.limit = limit;
        this._limits.push(config.limit);
        const behavior = config.begin(target);
        this._behaviors.push(behavior);
        if (this._dragStartCall) config.onDragStart = this._dragStartCall;
        if (this._dragProgressCall) config.onDragProgress = this._dragProgressCall;
        if (this._dragEndCall) config.onDragEnd = this._dragEndCall;
    }

    /**
     * 移除目标（成为不可拖动对象）
     * @param target 目标
     */
    public removeTarget(target: any): void {
        const configs = this._configs;
        const targets = this._targets;
        const index = this.findTarget(target);
        if (index < 0) return;
        configs[index].end(targets[index], this._behaviors[index]);
        targets.splice(index, 1);
        configs.splice(index, 1);
        this._limits.splice(index, 1);
        this._behaviors.splice(index, 1);
    }

    /**
     * 清除当前实例的所有拖动对象
     */
    public clear(): void {
        const targets = this._targets;
        for (let i = 0; i < targets.length; i++) {
            this.removeTarget(targets[i]);
            i--;
        }
    }

    /**
     * 获取目标相应的拖动部件
     * @param target 目标
     * @returns 拖动部件
     */
    public getDragComponents(target: any): IDragComponents {
        const index = this.findTarget(target);
        return {
            config: this._configs[index],
            limit: this._limits[index],
            behavior: this._behaviors[index]
        };
    }

    /**
     * 查询拖动对象在可拖动对象集合中的索引值
     * @param target 目标对象
     * @returns 索引值
     */
    private findTarget(target: any): number {
        const configs = this._configs;
        const targets = this._targets;
        let index = -1;
        for (let i = 0; i < targets.length; i++) {
            const conf = configs[i];
            if (conf.test(target)) {
                if (conf.compare(targets[i], target)) {
                    index = i;
                    break;
                }
            }
        }
        return index;
    }

}


export { Drag };
export type { IDragComponents, IDragCallback };